#!/usr/bin/env python3
#
# Copyright 2021 Graviti. Licensed under MIT License.
#

"""The implementation of the TensorBay polyline label."""

from typing import Any, Dict, Iterable, Optional, Type, TypeVar

from tensorbay.geometry import MultiPolyline2D, Polyline2D
from tensorbay.label.basic import SubcatalogBase, _LabelBase
from tensorbay.label.supports import AttributesMixin, CategoriesMixin, IsTrackingMixin
from tensorbay.utility import ReprType, attr, attr_base, camel, common_loads


class Polyline2DSubcatalog(SubcatalogBase, IsTrackingMixin, CategoriesMixin, AttributesMixin):
    """This class defines the subcatalog for 2D polyline type of labels.

    Arguments:
        is_tracking: A boolean value indicates whether the corresponding
            subcatalog contains tracking information.
        is_beizer_curve: A boolean value indicates whether the corresponding
            subcatalog contains beizer curve information.

    Attributes:
        description: The description of the entire 2D polyline subcatalog.
        categories: All the possible categories in the corresponding dataset
            stored in a :class:`~tensorbay.utility.name.NameList`
            with the category names as keys
            and the :class:`~tensorbay.label.supports.CategoryInfo` as values.
        category_delimiter: The delimiter in category values indicating parent-child relationship.
        attributes: All the possible attributes in the corresponding dataset
            stored in a :class:`~tensorbay.utility.name.NameList`
            with the attribute names as keys
            and the :class:`~tensorbay.label.attribute.AttributeInfo` as values.
        is_tracking: Whether the Subcatalog contains tracking information.
        is_beizer_curve: Whether the Subcatalog contains beizer curve information.

    Examples:
        *Initialization Method 1:* Init from ``Polyline2DSubcatalog.loads()`` method.

        >>> catalog = {
        ...     "POLYLINE2D": {
        ...         "isTracking": True,
        ...         "isBeizerCurve": True,
        ...         "categories": [{"name": "0"}, {"name": "1"}],
        ...         "attributes": [{"name": "gender", "enum": ["male", "female"]}],
        ...     }
        ... }
        >>> Polyline2DSubcatalog.loads(catalog["POLYLINE2D"])
        Polyline2DSubcatalog(
          (is_beizer_curve): True,
          (is_tracking): True,
          (categories): NameList [...],
          (attributes): NameList [...]
        )

        *Initialization Method 2:* Init an empty Polyline2DSubcatalog and then add the attributes.

        >>> from tensorbay.label import CategoryInfo, AttributeInfo
        >>> from tensorbay.utility import NameList
        >>> categories = NameList()
        >>> categories.append(CategoryInfo("a"))
        >>> attributes = NameList()
        >>> attributes.append(AttributeInfo("gender", enum=["female", "male"]))
        >>> polyline2d_subcatalog = Polyline2DSubcatalog()
        >>> polyline2d_subcatalog.is_tracking = True
        >>> polyline2d_subcatalog.is_beizer_curve = True
        >>> polyline2d_subcatalog.categories = categories
        >>> polyline2d_subcatalog.attributes = attributes
        >>> polyline2d_subcatalog
        Polyline2DSubcatalog(
          (is_beizer_curve): True,
          (is_tracking): True,
          (categories): NameList [...],
          (attributes): NameList [...]
        )

    """

    _repr_attrs = ("is_beizer_curve",) + SubcatalogBase._repr_attrs
    is_beizer_curve: bool = attr(key=camel, default=False)

    def __init__(self, is_tracking: bool = False, is_beizer_curve: bool = False) -> None:
        SubcatalogBase.__init__(self)
        IsTrackingMixin.__init__(self, is_tracking)
        self.is_beizer_curve = is_beizer_curve


class LabeledPolyline2D(_LabelBase, Polyline2D):
    """This class defines the concept of polyline2D label.

    :class:`LabeledPolyline2D` is the 2D polyline type of label,
    which is often used for CV tasks such as lane detection.

    Arguments:
        points: A list of 2D points representing the vertexes of the 2D polyline.
        category: The category of the label.
        attributes: The attributes of the label.
        instance: The instance id of the label.
        beizer_point_types: The beizer point types of the label.

    Attributes:
        category: The category of the label.
        attributes: The attributes of the label.
        instance: The instance id of the label.
        beizer_point_types: The beizer point types of the label.

    Examples:
        >>> LabeledPolyline2D(
        ...     [(1, 2), (2, 4), (2, 1)],
        ...     category="example",
        ...     attributes={"key": "value"},
        ...     instance="123",
        ...     beizer_point_types="LLL",
        ... )
        LabeledPolyline2D [
          Vector2D(1, 2),
          Vector2D(2, 4),
          Vector2D(2, 1)
        ](
          (beizer_point_types): 'LLL',
          (category): 'example',
          (attributes): {...},
          (instance): '123'
        )

    """

    _T = TypeVar("_T", bound="LabeledPolyline2D")

    _repr_type = ReprType.SEQUENCE
    _repr_attrs = ("beizer_point_types",) + _LabelBase._repr_attrs
    _attrs_base: Polyline2D = attr_base(key="polyline2d")
    beizer_point_types: str = attr(is_dynamic=True, key=camel)

    def __init__(
        self,
        points: Optional[Iterable[Iterable[float]]] = None,
        *,
        category: Optional[str] = None,
        attributes: Optional[Dict[str, Any]] = None,
        instance: Optional[str] = None,
        beizer_point_types: Optional[str] = None,
    ):
        Polyline2D.__init__(self, points)  # type: ignore[arg-type]
        _LabelBase.__init__(self, category, attributes, instance)
        if beizer_point_types:
            self.beizer_point_types = beizer_point_types

    @classmethod
    def loads(cls: Type[_T], contents: Dict[str, Any]) -> _T:  # type: ignore[override]
        """Loads a LabeledPolyline2D from a dict containing the information of the label.

        Arguments:
            contents: A dict containing the information of the 2D polyline label.

        Returns:
            The loaded :class:`LabeledPolyline2D` object.

        Examples:
            >>> contents = {
            ...     "polyline2d": [{'x': 1, 'y': 2}, {'x': 2, 'y': 4}, {'x': 2, 'y': 1}],
            ...     "category": "example",
            ...     "attributes": {"key": "value"},
            ...     "instance": "12345",
            ...     "beizer_point_types": "LLL",
            ... }
            >>> LabeledPolyline2D.loads(contents)
            LabeledPolyline2D [
              Vector2D(1, 2),
              Vector2D(2, 4),
              Vector2D(2, 1)
            ](
              (beizer_point_types): 'LLL',
              (category): 'example',
              (attributes): {...},
              (instance): '12345'
            )

        """
        return common_loads(cls, contents)

    def dumps(self) -> Dict[str, Any]:  # type: ignore[override]
        """Dumps the current 2D polyline label into a dict.

        Returns:
            A dict containing all the information of the 2D polyline label.

        Examples:
            >>> labeledpolyline2d = LabeledPolyline2D(
            ...     [(1, 2), (2, 4), (2, 1)],
            ...     category="example",
            ...     attributes={"key": "value"},
            ...     instance="123",
            ...     beizer_point_types="LLL",
            ... )
            >>> labeledpolyline2d.dumps()
            {
                'category': 'example',
                'attributes': {'key': 'value'},
                'instance': '123',
                'polyline2d': [{'x': 1, 'y': 2}, {'x': 2, 'y': 4}, {'x': 2, 'y': 1}],
                'beizerPointTypes': 'LLL',
            }

        """
        return self._dumps()


class MultiPolyline2DSubcatalog(SubcatalogBase, IsTrackingMixin, CategoriesMixin, AttributesMixin):
    """This class defines the subcatalog for 2D multiple polyline type of labels.

    Arguments:
        is_tracking: A boolean value indicates whether the corresponding
            subcatalog contains tracking information.

    Attributes:
        description: The description of the entire 2D multiple polyline subcatalog.
        categories: All the possible categories in the corresponding dataset
            stored in a :class:`~tensorbay.utility.name.NameList`
            with the category names as keys
            and the :class:`~tensorbay.label.supports.CategoryInfo` as values.
        category_delimiter: The delimiter in category values indicating parent-child relationship.
        attributes: All the possible attributes in the corresponding dataset
            stored in a :class:`~tensorbay.utility.name.NameList`
            with the attribute names as keys
            and the :class:`~tensorbay.label.attribute.AttributeInfo` as values.
        is_tracking: Whether the Subcatalog contains tracking information.

    Examples:
        *Initialization Method 1:* Init from ``MultiPolyline2DSubcatalog.loads()`` method.

        >>> catalog = {
        ...     "MULTI_POLYLINE2D": {
        ...         "isTracking": True,
        ...         "categories": [{"name": "0"}, {"name": "1"}],
        ...         "attributes": [{"name": "gender", "enum": ["male", "female"]}],
        ...     }
        ... }
        >>> MultiPolyline2DSubcatalog.loads(catalog["MULTI_POLYLINE2D"])
        MultiPolyline2DSubcatalog(
          (is_tracking): True,
          (categories): NameList [...],
          (attributes): NameList [...]
        )

        *Initialization Method 2:* Init an empty MultiPolyline2DSubcatalog
        and then add the attributes.

        >>> from tensorbay.label import CategoryInfo, AttributeInfo
        >>> multi_polyline2d_subcatalog = MultiPolyline2DSubcatalog()
        >>> multi_polyline2d_subcatalog.is_tracking = True
        >>> multi_polyline2d_subcatalog.add_category(CategoryInfo("a"))
        >>> multi_polyline2d_subcatalog.add_attribute(
            AttributeInfo("gender", enum=["female", "male"]))
        >>> multi_polyline2d_subcatalog
        MultiPolyline2DSubcatalog(
          (is_tracking): True,
          (categories): NameList [...],
          (attributes): NameList [...]
        )

    """

    def __init__(self, is_tracking: bool = False) -> None:
        SubcatalogBase.__init__(self)
        IsTrackingMixin.__init__(self, is_tracking)


class LabeledMultiPolyline2D(_LabelBase, MultiPolyline2D):  # type: ignore[misc]
    """This class defines the concept of multiPolyline2D label.

    :class:`LabeledMultiPolyline2D` is the 2D multiple polyline type of label,
    which is often used for CV tasks such as lane detection.

    Arguments:
        polylines: A list of polylines.
        category: The category of the label.
        attributes: The attributes of the label.
        instance: The instance id of the label.

    Attributes:
        category: The category of the label.
        attributes: The attributes of the label.
        instance: The instance id of the label.

    Examples:
        >>> LabeledMultiPolyline2D(
        ...     [[[1, 2], [2, 3]], [[3, 4], [6, 8]]],
        ...     category="example",
        ...     attributes={"key": "value"},
        ...     instance="123",
        ... )
        LabeledPolyline2D [
          Polyline2D [...]
          Polyline2D [...]
        ](
          (category): 'example',
          (attributes): {...},
          (instance): '123'
        )

    """

    _T = TypeVar("_T", bound="LabeledMultiPolyline2D")

    _repr_type = ReprType.SEQUENCE
    _repr_attrs = _LabelBase._repr_attrs
    _attrs_base: MultiPolyline2D = attr_base(key="multiPolyline2d")

    def __init__(
        self,
        polylines: Optional[Iterable[Iterable[float]]] = None,
        *,
        category: Optional[str] = None,
        attributes: Optional[Dict[str, Any]] = None,
        instance: Optional[str] = None,
    ):
        MultiPolyline2D.__init__(self, polylines)  # type: ignore[arg-type]
        _LabelBase.__init__(self, category, attributes, instance)

    @classmethod
    def loads(cls: Type[_T], contents: Dict[str, Any]) -> _T:  # type: ignore[override]
        """Loads a LabeledMultiPolyline2D from a dict containing the information of the label.

        Arguments:
            contents: A dict containing the information of the 2D polyline label.

        Returns:
            The loaded :class:`LabeledMultiPolyline2D` object.

        Examples:
            >>> contents = {
            ...     "multiPolyline2d": [[{'x': 1, 'y': 1}, {'x': 1, 'y': 2}, {'x': 2, 'y': 2}],
                                        [{'x': 2, 'y': 3}, {'x': 3, 'y': 5}]],
            ...     "category": "example",
            ...     "attributes": {"key": "value"},
            ...     "instance": "12345",
            ... }
            >>> LabeledMultiPolyline2D.loads(contents)
            LabeledMultiPolyline2D [
              Polyline2D [...]
              Polyline2D [...]
            ](
              (category): 'example',
              (attributes): {...},
              (instance): '12345'
            )

        """
        return common_loads(cls, contents)

    def dumps(self) -> Dict[str, Any]:  # type: ignore[override]
        """Dumps the current 2D multiple polyline label into a dict.

        Returns:
            A dict containing all the information of the 2D polyline label.

        Examples:
            >>> labeledmultipolyline2d = LabeledMultiPolyline2D(
            ...     [[[1, 1], [1, 2], [2, 2]], [[2, 3], [3, 5]]],
            ...     category="example",
            ...     attributes={"key": "value"},
            ...     instance="123",
            ... )
            >>> labeledpolyline2d.dumps()
            {
                'category': 'example',
                'attributes': {'key': 'value'},
                'instance': '123',
                'polyline2d': [
                    [{'x': 1, 'y': 1}, {'x': 1, 'y': 2}, {'x': 2, 'y': 2}],
                    [{'x': 2, 'y': 3}, {'x': 3, 'y': 5}],
            }

        """
        return self._dumps()
